using System;
using System.Collections.Generic;
using System.Linq;
using Magiq.Update;

namespace Magiq.Databases.Update
{
    public class MultipleTableUpdateEmulation<T> where T : class
    {
        private readonly IOrmAdapter ormAdapter;
        private readonly Dictionary<string, TableUpdate<T>> tables = new Dictionary<string, TableUpdate<T>>();
        private readonly IUpdate<T> update;

        public MultipleTableUpdateEmulation(IUpdate<T> update, IOrmAdapter ormAdapter)
        {
            this.update = update;
            this.ormAdapter = ormAdapter;
            var source = update.Source;

            foreach (var propertySet in update.PropertySets)
            {
                var key = propertySet.ParentKey;

                TableUpdate<T> tableUpdate;
                if (!tables.TryGetValue(key, out tableUpdate))
                {
                    tableUpdate = new TableUpdate<T>(source);
                    tables.Add(key, tableUpdate);
                }
                tableUpdate.Set(propertySet);
            }
        }

        public int AmountOfTables
        {
            get { return tables.Count; }
        }

        public IEnumerable<TableUpdate<T>> Tables
        {
            get { return tables.Values; }
        }

        public IEnumerable<DatabaseUpdate<T>> GetTableUpdates()
        {
            var updates = new List<DatabaseUpdate<T>>();
            if (AmountOfTables == 1)
            {
                updates.Add(GetDatabaseUpdate(Tables.Single()));
            }
            else
            {
                var invalidatingUpdates = Tables.Where(x => x.IsWhereInvalidation).ToList();

                if (invalidatingUpdates.Count > 1)
                    throw new NotSupportedException(
                        "Update to several tables is not supported. However, Magiq will execute several updates whenever it's possible, but now it's not the case because the query you are trying to execute contains several updates to columns that are being used in the where clause. Try changing the query.");

                updates.AddRange(Tables.Where(x => !x.IsWhereInvalidation).Select(GetDatabaseUpdate));

                var invalidatingUpdate = invalidatingUpdates.SingleOrDefault();

                if (invalidatingUpdate != null)
                    updates.Add(GetDatabaseUpdate(invalidatingUpdate));
            }
            return updates;
        }

        private DatabaseUpdate<T> GetDatabaseUpdate(TableUpdate<T> tableUpdate)
        {
            var samplePropertySet = tableUpdate.PropertySets.First();
            var mapping = ormAdapter.GetMappingOf(samplePropertySet.DeclaringType);

            var databaseUpdate = new DatabaseUpdate<T>
                                     {
                                         OrmAdapter = ormAdapter,
                                         SourceTypeMapping = mapping,
                                         Source = update.Source
                                     };

            foreach (var set in tableUpdate.PropertySets)
            {
                databaseUpdate.ColumnAssignments.Add(new UpdateColumnsAssignment
                                                         {
                                                             Columns = mapping.ColumnsFor(set.PropertyName),
                                                             ValueExpression = set.ValueExpression,
                                                             DeclaringType = set.DeclaringType,
                                                             PropertyExpression = set.PropertyExpression,
                                                             PropertyName = set.PropertyName,
                                                             PropertyType = set.PropertyType,
                                                             ValueType = set.ValueType
                                                         });
            }
            return databaseUpdate;
        }
    }
}